using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

using Atomic.Libraries.Mathematics;
using Atomic.Structures;

namespace Atomic.Interfaces.Atat
{
	public static class AtatStructure
	{
		/// <summary>
		/// Reads structure in ATAT format.
		/// </summary>
		public static Structure Read(FileInfo file)
		{
			Structure structure;
			using (StreamReader reader = new StreamReader(file.FullName))
			{
				structure = Read(reader);
				reader.Close();
			}

			return structure;
		}

		/// <summary>
		/// Reads structure in ATAT format.
		/// </summary>
		public static Structure Read(TextReader reader)
		{
			// One or three lines for the basis vectors of the coordinate system.
			Basis basis;
			double[] a = AtomicEnvironment.ParseArray(reader.ReadLine());
			if (a.Length == 6)
			{
				// a b c alpha beta gamma notation.
				basis = new Basis(a[0], a[1], a[2], a[3], a[4], a[5]);
			}
			else if (a.Length == 3)
			{
				// Try reading two additional lines.
				SpaceVector b = AtomicEnvironment.ParseSpaceVector(reader.ReadLine());
				SpaceVector c = AtomicEnvironment.ParseSpaceVector(reader.ReadLine());
				basis = new Basis(new SpaceVector(a), b, c); 
			}
			else
			{
				throw new Exception("Unrecognized ATAT coordinate system format.");
			}

			// Lattice vectors (defined relative to the coordinate system). Always three lines.
			Lattice lattice = Lattice.Parse(basis, AtomicEnvironment.ParseSpaceVector(reader.ReadLine()), AtomicEnvironment.ParseSpaceVector(reader.ReadLine()), AtomicEnvironment.ParseSpaceVector(reader.ReadLine()));

			List<Site> sites = new List<Site>();
			string s;
			while ((s = reader.ReadLine()) != null)
			{
				s = s.Trim(' ', '\t');
				if (s != "")
				{
					int i = s.TrimEnd(' ', '\t').LastIndexOfAny(new char[] { ' ', '\t' });
					if (i == -1)
					{
						throw new ArgumentException("Malformed site specification.");
					}
				
					// Positions are defined relative to the basis vectors of the coordinate system.
					SpaceVector p = AtomicEnvironment.ParseSpaceVector(s.Substring(0, i));
					sites.Add(new Site(Atom.Parse(s.Substring(i + 1), true), new Position(basis, p)));
				}
			}

			return new Structure(basis, lattice, sites);
		}

		/// <summary>
		/// Reads structure in ATAT format.
		/// </summary>
		public static Structure Read(string s)
		{
			Structure structure;
			using (StringReader reader = new StringReader(s))
			{
				structure = Read(reader);
				reader.Close();
			}

			return structure;
		}

		/// <summary>
		/// Writes structure in ATAT format.
		/// </summary>
		public static void Write(Structure structure, FileInfo file)
		{
			using (StreamWriter writer = new StreamWriter(file.FullName))
			{
				Write(structure, writer);
				writer.Close();
			}
		}

		/// <summary>
		/// Writes structure in ATAT format.
		/// </summary>
		public static void Write(Structure structure, TextWriter writer)
		{
			Basis basis = structure.Basis;
			Lattice lattice = structure.Lattice;

			// Write the coordinate basis vectors (not relative to any other basis, i.e. physical coordinates)
			// followed by the lattice vectors (relative to this basis); one vector per line.
			foreach (SpaceVector p in new SpaceVector[] { basis.A.PhysicalCoordinates, basis.B.PhysicalCoordinates, basis.C.PhysicalCoordinates,
				basis.Decompose(lattice.A).Coordinates, basis.Decompose(lattice.B).Coordinates, basis.Decompose(lattice.C).Coordinates })
			{
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r}", p.X, p.Y, p.Z));
			}

			foreach (Site site in structure.Sites)
			{
				// Use coordinates relative to the basis of the coordinate system.
				SpaceVector p = basis.Decompose(site.Position).Coordinates;
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r} {3}", p.X, p.Y, p.Z, site.Atom.Symbol));
			}
		}

		/// <summary>
		/// Writes structure in ATAT format.
		/// </summary>
		public static string Write(Structure structure)
		{
			using (StringWriter writer = new StringWriter())
			{
				Write(structure, writer);
				writer.Close();
				return writer.ToString();
			}
		}

		public static void WriteDirect(Structure structure, FileInfo file)
		{
			using (StreamWriter writer = new StreamWriter(file.FullName))
			{
				WriteDirect(structure, writer);
				writer.Close();
			}
		}

		public static void WriteDirect(Structure structure, TextWriter writer)
		{
			Lattice lattice = structure.Lattice;

			// Use lattice vectors as the basis.
			foreach (SpaceVector p in new SpaceVector[] { lattice.A.PhysicalCoordinates, lattice.B.PhysicalCoordinates, lattice.C.PhysicalCoordinates })
			{
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r}", p.X, p.Y, p.Z));
			}
						
			// Then the lattice itself is just the identity.
			writer.WriteLine("1 0 0");
			writer.WriteLine("0 1 0");
			writer.WriteLine("0 0 1");

			foreach (Site site in structure.Sites)
			{
				// Decompose in terms of the lattice (i.e. the basis used).
				SpaceVector p = lattice.Decompose(site.Position).Coordinates;
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r} {3}", p.X, p.Y, p.Z, site.Atom.Symbol));
			}
		}

		public static string WriteDirect(Structure structure)
		{
			using (StringWriter writer = new StringWriter())
			{
				WriteDirect(structure, writer);
				writer.Close();
				return writer.ToString();
			}
		}

		public static void WriteCartesian(Structure structure, FileInfo file)
		{
			using (StreamWriter writer = new StreamWriter(file.FullName))
			{
				WriteCartesian(structure, writer);
				writer.Close();
			}
		}

		public static void WriteCartesian(Structure structure, TextWriter writer)
		{
			Lattice lattice = structure.Lattice;
			
			writer.WriteLine("1 0 0");
			writer.WriteLine("0 1 0");
			writer.WriteLine("0 0 1");

			foreach (SpaceVector p in new SpaceVector[] { lattice.A.PhysicalCoordinates, lattice.B.PhysicalCoordinates, lattice.C.PhysicalCoordinates })
			{
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r}", p.X, p.Y, p.Z));
			}

			foreach (Site site in structure.Sites)
			{
				SpaceVector p = site.Position.PhysicalCoordinates;
				writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:r} {1:r} {2:r} {3}", p.X, p.Y, p.Z, site.Atom.Symbol));
			}
		}

		public static string WriteCartesian(Structure structure)
		{
			using (StringWriter writer = new StringWriter())
			{
				WriteCartesian(structure, writer);
				writer.Close();
				return writer.ToString();
			}
		}
	}
}
